#include <QtGui/QContextMenuEvent>
#include <QtGui/QHBoxLayout>
#include <QtGui/QToolButton>
#include <QtGui/QListWidget>
#include <QtCore/QSettings>
#include <Qsci/qscilexer.h>
#include <QDebug>
#include "Const.h"
#include "Editor.h"

Editor::Editor(QWidget *parent) : QsciScintilla(parent),editorIndex(-1) {
	qDebug() << "[Editor] Start creation of new editor";
	
	//=================Buttons creation==================//
	
	zoomInButton = new QToolButton;
	zoomInButton->setIcon(QIcon(":/icons/zoom_in.png"));
	zoomInButton->setShortcut(QKeySequence("Plus"));
	zoomInButton->setIconSize(QSize(22, 22));
	zoomInButton->setAutoRaise(true);
	zoomInButton->setToolTip(tr("Zoom in"));
	
	zoomOutButton = new QToolButton;
	zoomOutButton->setIcon(QIcon(":/icons/zoom_out.png"));
	zoomOutButton->setShortcut(QKeySequence("Minus"));
	zoomOutButton->setIconSize(QSize(22, 22));
	zoomOutButton->setAutoRaise(true);
	zoomOutButton->setToolTip(tr("Zoom out"));
	
	showFilesListButton = new QToolButton;
	showFilesListButton->setIcon(QIcon(":/icons/view.png"));
	showFilesListButton->setIconSize(QSize(22, 22));
	showFilesListButton->setAutoRaise(true);
	showFilesListButton->setToolTip(tr("Show/Hide files list"));
	
	//=================Frames creations==================//
	
	buttonsFrame = new QFrame;
	buttonsFrame->setFrameShape(QFrame::Box);
	buttonsFrame->setFrameShadow(QFrame::Raised);
	buttonsFrame->setStyleSheet("QFrame { margin-right:8px; border: 1px solid orange; border-radius: 4px; background-color: rgb(228, 245, 250, 90); padding:2px;}");
	
	QFrame *verticalFrame = new QFrame;
	verticalFrame->setFrameStyle(QFrame::VLine | QFrame::Sunken);
	
	buttonsFrameLayout = new QHBoxLayout;
	buttonsFrameLayout->addWidget(zoomOutButton);
	buttonsFrameLayout->addWidget(zoomInButton);
	buttonsFrameLayout->addWidget(verticalFrame);
	buttonsFrameLayout->addWidget(showFilesListButton);
	buttonsFrameLayout->setContentsMargins(0,0,0,0);
	buttonsFrame->setLayout(buttonsFrameLayout);
	buttonsFrame->setFixedSize(buttonsFrameLayout->minimumSize());
	
	//===============Other widgets creations================//
	
	filesListWidget = new QListWidget;
	filesListWidget->setMaximumWidth(100);
	filesListWidget->setStyleSheet("QListWidget { border: 1px solid orange; border-radius: 3px; background-color: rgb(228, 245, 250, 90); } ");
	
	//===============Layouts=====================//
	
	QHBoxLayout *topLayout = new QHBoxLayout;
	topLayout->addStretch();
	topLayout->addWidget(buttonsFrame);
	
	QVBoxLayout *verticalLayout = new QVBoxLayout;
	verticalLayout->addWidget(filesListWidget);
	verticalLayout->addStretch();
	
	QHBoxLayout *bottomLayout = new QHBoxLayout;
	bottomLayout->addStretch();
	bottomLayout->addLayout(verticalLayout);
	bottomLayout->setContentsMargins(0,5,10,0);
	
	QVBoxLayout *mainLayout = new QVBoxLayout;
	mainLayout->addLayout(topLayout);
	mainLayout->addLayout(bottomLayout);
	setLayout(mainLayout);
	
	//================Connections===============//
	
	connect(zoomInButton,SIGNAL(clicked()),this,SLOT(zoomIn()));
	connect(zoomOutButton,SIGNAL(clicked()),this,SLOT(zoomOut()));
	connect(filesListWidget,SIGNAL(currentRowChanged(int)),this,SLOT(emitSignalToChangeFile()));
	connect(showFilesListButton,SIGNAL(clicked()),this,SLOT(showOrHideFilesList()));
	
	//================Default settings==============//
	setUtf8(true);
	setFolding(QsciScintilla::BoxedTreeFoldStyle);
	setAutoIndent(true);
	setBraceMatching(QsciScintilla::SloppyBraceMatch);
	setAutoCompletionSource(QsciScintilla::AcsAll);
	setAutoCompletionThreshold(2);
	setBackspaceUnindents(true);
	setMatchedBraceBackgroundColor(QColor(255,255,0));
	setMarginMarkerMask(1,6);
	setSelectionBackgroundColor(QColor(255,185,185));
	setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
	markerDefine(QsciScintilla::RightTriangle, 1);
	markerDefine(QsciScintilla::Background, 2);
	setMarkerForegroundColor(QColor(100, 100, 100));
	setMarkerBackgroundColor(QColor(255,255,150));
	
	updateSettings();
	
	qDebug() << "[Editor] Editor was created";
}

bool Editor::find(const QString& expression,bool regExp,bool caseSensitive,bool wholeWord,bool forward,bool fromCursor) {
	qDebug() << "[Editor] Start find string" <<  expression << "with options:"
			<< "regular expression -" << regExp
			<< ",case sensitive - " << caseSensitive
			<< ",whole word - " << wholeWord
			<< ",forward - " << forward;
	
	if (!fromCursor)
		setCursorPosition(0,0);
	
	if (hasSelectedText()) {
		int selectionStartLine(0),selectionStartCursor(0);
		getSelection(&selectionStartLine,&selectionStartCursor,new int,new int);
		setCursorPosition(selectionStartLine,selectionStartCursor);
	}
	
	currentFindEntryIndex = -1;
	previousFindEntries.clear();
	
	if (!findFirst(expression,regExp,caseSensitive,wholeWord,true,forward)) {
		qDebug() << "[Editor] Find finished with false";
		return false;
	}
	
	previousFindEntries << getCurrentSelection();
	currentFindEntryIndex = previousFindEntries.count() - 1;
	
	qDebug() << "[Editor] Find finished with true";
	return true;
}

bool Editor::findPreviousEntry() {
	qDebug() << "[Editor] Find previous entry. Current entry index =" << currentFindEntryIndex;
	
	if (currentFindEntryIndex > 0) { 
		currentFindEntryIndex--;
		if (currentFindEntryIndex < previousFindEntries.count()) {
			setCurrentSelection(previousFindEntries[currentFindEntryIndex]);
			return true;
		}
	} else {
		currentFindEntryIndex = previousFindEntries.count();
		return findPreviousEntry();
	}
	return false;
}

bool Editor::findNextEntry() {
	qDebug() << "[Editor] Find next entry. Current entry index =" << currentFindEntryIndex;
	
	if (currentFindEntryIndex != -1 && currentFindEntryIndex + 1 < previousFindEntries.count()) {
		setCurrentSelection(previousFindEntries[++currentFindEntryIndex]);
		return true;
	}	
	if (findNext()) {
		previousFindEntries << getCurrentSelection();
		currentFindEntryIndex = previousFindEntries.count() - 1;
		return true;
	}
	return false;
}

void Editor::setCurrentSelection(const Selection& selection) {
	setSelection(selection.startLine,selection.startLineIndex,selection.endLine,selection.endLineIndex);
}

Selection Editor::getCurrentSelection() const {
	int startLine(-1),startLineIndex(-1),endLine(-1),endLineIndex(-1);
	getSelection(&startLine,&startLineIndex,&endLine,&endLineIndex);

	return Selection(startLine,startLineIndex,endLine,endLineIndex);
}

bool Editor::findAndReplaceAllEntries(const QString& expression,const QString& replaceOn,
					bool regExp,bool caseSensitive,bool wholeWord,bool forward,bool fromCursor) {
	qDebug() << "[Editor] Replace string" <<  replaceOn << "with options:"
			<< "find string -" << expression 
			<< ",regular expression -" << regExp
			<< ",case sensitive - " << caseSensitive
			<< ",whole word - " << wholeWord
			<< ",forward - " << forward;
	int startReplaceLine(0),startReplaceLineIndex(0),endReplaceLine(lines()),endReplaceLineIndex(lineLength(lines()-1));

	if (hasSelectedText())
		getSelection(&startReplaceLine,&startReplaceLineIndex,&endReplaceLine,&endReplaceLineIndex);
	else
		getCursorPosition(&startReplaceLine,&startReplaceLineIndex);
	setCursorPosition(startReplaceLine,startReplaceLineIndex);
	
	if (!find(expression,regExp,caseSensitive,wholeWord,forward,fromCursor))
		return false;
	
	beginUndoAction();
	replace(replaceOn);
	int newSelectionStartLine(-1),newSelectionStartIndex(-1);
	while (findNext()) {
		getSelection(&newSelectionStartLine,&newSelectionStartIndex,new int,new int);
		if (newSelectionStartLine > endReplaceLine)
			break;
		else if (newSelectionStartLine == endReplaceLine)
			if (newSelectionStartIndex >= endReplaceLineIndex)
				break;
		replace(replaceOn);
	}
	endUndoAction();
	
	setCursorPosition(startReplaceLine,startReplaceLineIndex);
	
	return true;
}

void Editor::moveToLineNumber(int lineNumber) {
	qDebug() << "[Editor] Move to line number" << lineNumber;
	
	setFocus();
	
	int cursorPositionIndex(0);
	getCursorPosition(new int,&cursorPositionIndex);
	setCursorPosition(lineNumber-1,cursorPositionIndex);
}

void Editor::unindentSelectedText() {
	beginUndoAction();
	if (hasSelectedText()) {
		Selection selection = getCurrentSelection();
		for (int line = selection.startLine; line <= selection.endLine; line++)
			unindent(line);
	} else {
		int line(-1);
		getCursorPosition(&line,new int);
		unindent(line);
	}
	endUndoAction();
}

void Editor::commentSelectedText() {
	// TODO: add support of another languages and not only C++
	
	int commentLineFrom(-1),commentLineTo(-1),cursorPosIndex(0);
	if (hasSelectedText())
		getSelection(&commentLineFrom,new int,&commentLineTo,&cursorPosIndex);
	else {
		getCursorPosition(&commentLineFrom,&cursorPosIndex);
		commentLineTo = commentLineFrom;
	}
	beginUndoAction();
	if (commentLineTo - commentLineFrom >= 8) {
		insertAt("/*\n",commentLineFrom,0);
		commentLineTo++;
		if (commentLineTo + 1 >= lines())
			insertAt("\n*/\n",commentLineTo+1,lineLength(commentLineTo+1)-1);
		else
			insertAt("*/\n",commentLineTo+1,0);
	} else
		for (int line = commentLineFrom; line <= commentLineTo; line++)
			insertAt("//",line,0);
	cursorPosIndex += 2;
	setCursorPosition(commentLineTo,cursorPosIndex);
	endUndoAction();
}

void Editor::setBookmark() {
	int currentLine = currentLineNumber();
	
	if (markersAtLine(currentLine) & 2) {
		markerDelete(currentLine,1);
		markerDelete(currentLine,2);
	} else {
		markerAdd(currentLine,1);
		markerAdd(currentLine,2);
	}
}

void Editor::nextBookmark() {
	int nextMarkerLine = markerFindNext(currentLineNumber() + 1,2);
	if (nextMarkerLine >= 0)
		moveToLineNumber(nextMarkerLine + 1);
	else {
		nextMarkerLine = markerFindNext(0,2);
		if (nextMarkerLine >= 0)
			moveToLineNumber(nextMarkerLine + 1);
	}
}

void Editor::previousBookmark() {
	int previousMarkerLine = markerFindPrevious(currentLineNumber() - 1,2);
	if (previousMarkerLine >= 0)
		moveToLineNumber(previousMarkerLine + 1);
	else {
		previousMarkerLine = markerFindPrevious(lines() - 1,2);
		if (previousMarkerLine >= 0)
			moveToLineNumber(previousMarkerLine + 1);
	}
}

void Editor::unsetAllBookmarks() {
	markerDeleteAll();
}

int Editor::currentLineNumber() {
	int currentLine(0);
	getCursorPosition(&currentLine,new int);
	return currentLine;
}

void Editor::updateMarginWidth() {
	if (marginLineNumbers(1))
		if (lines() > 100)
			setMarginWidth(1, QString("00%1").arg(lines()));
		else
			setMarginWidth(1, QString("0100"));
	else
		setMarginWidth(1, 0);
}

void Editor::showLinesNumbers(bool show) {
	setMarginLineNumbers(1,show);
	updateMarginWidth();
}

void Editor::adjustTextByWidth(bool adjust) {
	setWrapMode(adjust ? QsciScintilla::WrapWord : QsciScintilla::WrapNone);
}

void Editor::updateSettings() {
	QSettings settings(ORGANIZATION,PROGRAM_NAME);
	QFont font = QFont(settings.value("Settings/Font","DejaVu Sans Mono").toString(),
						settings.value("Settings/FontSize",10).toInt());
	lexer() ? lexer()->setFont(font) : setFont(font);
	setIndentationWidth(settings.value("Settings/TabSize",4).toInt());
	setIndentationsUseTabs(!settings.value("Settings/ReplaceTabsBySpaces",false).toBool());
	setIndentationGuides(settings.value("Settings/ShowIndents",true).toBool());
	setMarginLineNumbers(1,settings.value("Settings/ShowLinesNumbers",true).toBool());
	adjustTextByWidth(settings.value("Settings/AdjustTextByWidth",true).toBool());
	setCaretLineVisible(settings.value("Settings/HighlightCurrentLine",true).toBool());
	setCaretLineBackgroundColor(settings.value("Settings/CurentLineColor",QColor(225,250,255)).value<QColor>());
	setAutoCompletionReplaceWord(settings.value("Settings/AutoComplReplaceWords",true).toBool());
	setAutoCompletionThreshold(settings.value("Settings/AutoComplSensivity",2).toInt());
	bool useAutoCompletion = settings.value("Settings/UseAutoCompletion",false).toBool();
	if (!useAutoCompletion)
		setAutoCompletionSource(QsciScintilla::AcsNone);
	else
		setAutoCompletionSource(QsciScintilla::AcsDocument);
	updateMarginWidth();
}

void Editor::contextMenuEvent(QContextMenuEvent *event) {
	emit (contextMenuRequested(event->globalX(),event->globalY()));
}


void Editor::addItemToFilesList(int index,const QString& title) {
	QListWidgetItem *newItem = new QListWidgetItem(title);
	newItem->setData(Qt::UserRole,index);
	filesListWidget->addItem(newItem);
	
	filesListWidget->sortItems();
	
	updateFilesListSize();
}

void Editor::emitSignalToChangeFile() {
	emit(changeFileRequested(filesListWidget->currentItem()->data(Qt::UserRole).toInt()));
}

void Editor::showOrHideFilesList() {
	emit(filesListStatusChanged(filesListWidget->isHidden()));
}

void Editor::setFilesListStatus(bool show) {
	filesListWidget->setVisible(show);
}

void Editor::setEditorIndex(int index) {
	editorIndex = index;
}

int Editor::getEditorIndex() const {
	return editorIndex;
}

void Editor::selectItemInFilesList() {
	for (int i = 0; i < filesListWidget->count(); i++)
		if (filesListWidget->item(i)->data(Qt::UserRole).toInt() == editorIndex) {
			filesListWidget->blockSignals(true);
			filesListWidget->setCurrentRow(i);
			filesListWidget->blockSignals(false);
			break;
		}
}

void Editor::updateFilesListSize() {
	int height = 15*filesListWidget->count();
	
	if (height > size().height() - 100)
		height = size().height() - 100;
	
	int width = 0;;
	for (int i = 0; i < filesListWidget->count(); i++)
		if (filesListWidget->item(i)->text().length()*7 > width)
			width = filesListWidget->item(i)->text().length()*7;
	if (width > 200)
		width = 200;
	if (filesListWidget->width() < width)
		filesListWidget->setMaximumWidth(width);
	
	filesListWidget->setMaximumHeight(height+20);
	filesListWidget->setMinimumHeight(height+20);
}

void Editor::changeItemTitleInFilesList(int index,const QString& title) {
	for (int i = 0; i < filesListWidget->count(); i++)
		if (filesListWidget->item(i)->data(Qt::UserRole).toInt() == index) {
			filesListWidget->item(i)->setText(title);
			break;
		}
	updateFilesListSize();
}

void Editor::removeItemFromFilesList(int index) {
	qDebug() << "[Editor] Remove item from files list with index" << index;
	for (int i = 0; i < filesListWidget->count(); i++)
		if (filesListWidget->item(i)->data(Qt::UserRole).toInt() == index) {
			filesListWidget->blockSignals(true);
			delete filesListWidget->takeItem(i);
			filesListWidget->blockSignals(false);
			break;
		}
}

void Editor::reduceIndexOfItemsSince(int index) {
	for (int i = index; i < filesListWidget->count(); i++)
		filesListWidget->item(i)->setData(Qt::UserRole,filesListWidget->item(i)->data(Qt::UserRole).toInt() - 1);
}

void Editor::resizeEvent(QResizeEvent *) {
	updateFilesListSize();
}
